#!/bin/bash
# $Id: ae2df0486b7e7a68c3a05dd64b590b78086888ae $

PATH="${PATH}:/sbin:/usr/sbin"
GK_V='4.0.1'

GK_TIME_START=$(date +%s)

TODEBUGCACHE=yes # Until an error occurs or LOGFILE is fully qualified.

small_die() {
	echo "ERROR: $*" >&2
	exit 1
}

# We don't know where our config is, so we check for it, and default to using
# /etc/genkernel.conf if nobody has specified one.

# NOTE: We are look for --config=... in a way that doesn't modify $@ since we access that again, later
for arg in "$@"
do
	[[ "${arg}" == --config=* ]] && CMD_GK_CONFIG=${arg#--config=}
done

# Pull in our configuration to get GK_SHARE only...
_GENKERNEL_CONF=${CMD_GK_CONFIG:-/etc/genkernel.conf}
GK_SHARE=$(source "${_GENKERNEL_CONF}" &>/dev/null && echo ${GK_SHARE})

if [ -z "${GK_SHARE}" ]
then
	small_die "GK_SHARE is not set. Please check used genkernel config file at '${_GENKERNEL_CONF}'!"
fi

# Make sure that we do not clash with the environment
GK_DETERMINEARGS_FILE="${GK_SHARE}/gen_determineargs.sh"
GK_SETTINGS=( $(awk '/[^#]set_config_with_override/ { print $3 }' "${GK_DETERMINEARGS_FILE}" 2>/dev/null) )
if [ ${#GK_SETTINGS[@]} -gt 0 ]
then
	GK_SETTINGS+=( CALLBACK )
	GK_SETTINGS+=( EXTRAVERSION )
	GK_SETTINGS+=( MOD_INSTALL )

	for GK_SETTING in "${GK_SETTINGS[@]}"
	do
		for var_to_unset in ${GK_SETTING} CMD_${GK_SETTING}
		do
			if [ -n "${!var_to_unset}" ]
			then
				echo "WARNING: Will unset existing variable '${var_to_unset}' to avoid clashing with genkernel config ..." >&2
				unset ${var_to_unset} || small_die "Failed to unset existing variable '${var_to_unset}'!"
			fi
		done
	done

	unset GK_DETERMINEARGS_FILE GK_SETTINGS GK_SETTING var_to_unset
else
	small_die "Failed to extract genkernel options from '${GK_DETERMINEARGS_FILE}'!"
fi

# Now we can source our configuration...
source "${_GENKERNEL_CONF}" || small_die "Could not read ${_GENKERNEL_CONF}"

# set default LOGLEVEL if uninitialized
LOGLEVEL=${LOGLEVEL:-1}

# Start sourcing other scripts
source "${GK_SHARE}"/defaults/software.sh || small_die "Could not read '${GK_SHARE}/defaults/software.sh'"
source "${GK_SHARE}"/defaults/config.sh || small_die "Could not read '${GK_SHARE}/defaults/config.sh'"
source "${GK_SHARE}"/gen_funcs.sh || small_die "Could not read '${GK_SHARE}/gen_funcs.sh'"
source "${GK_SHARE}"/gen_cmdline.sh || small_die "Could not read '${GK_SHARE}/gen_cmdline.sh'"
source "${GK_SHARE}"/gen_arch.sh || small_die "Could not read '${GK_SHARE}/gen_arch.sh'"
source "${GK_SHARE}"/gen_determineargs.sh || small_die "Could not read '${GK_SHARE}/gen_determineargs.sh'"
source "${GK_SHARE}"/gen_compile.sh || small_die "Could not read '${GK_SHARE}/gen_compile.sh'"
source "${GK_SHARE}"/gen_configkernel.sh || small_die "Could not read '${GK_SHARE}/gen_configkernel.sh'"
source "${GK_SHARE}"/gen_initramfs.sh || small_die "Could not read '${GK_SHARE}/gen_initramfs.sh'"
source "${GK_SHARE}"/gen_moddeps.sh || small_die "Could not read '${GK_SHARE}/gen_moddeps.sh'"
source "${GK_SHARE}"/gen_package.sh || small_die "Could not read '${GK_SHARE}/gen_package.sh'"
source "${GK_SHARE}"/gen_bootloader.sh || small_die "Could not read '${GK_SHARE}/gen_bootloader.sh'"

export GK_MASTER_PID=${BASHPID}

set_default_gk_trap

BUILD_KERNEL="no"
BUILD_RAMDISK="no"
BUILD_MODULES="no"

# Parse all command line options...
GK_OPTIONS=$* # Save for later
while [ $# -gt 0 ]
do
	GK_OPTION=$1; shift
	parse_cmdline ${GK_OPTION}
done
unset GK_OPTION

# Check if no action is specified...
if ! isTrue "${BUILD_KERNEL}" && ! isTrue "${BUILD_RAMDISK}"
then
	usage
	exit 1
fi

NORMAL=${GOOD} print_info 1 "Gentoo Linux Genkernel; Version ${GK_V}${NORMAL}"
print_info 1 "Using genkernel configuration from '${_GENKERNEL_CONF}' ..."
unset _GENKERNEL_CONF
print_info 1 "Running with options: ${GK_OPTIONS}"

if ! hash grep &>/dev/null
then
	gen_die "grep not found. Is sys-apps/grep installed?"
fi

if ! hash zgrep &>/dev/null
then
	print_warning 1 "zgrep not found. Is app-arch/gzip installed? You will be unable to use compressed config files!"
fi

# Save any customizations of MODULES_* first.
override_module_vars="$(compgen -A variable |grep '^MODULES_')"
for v in ${override_module_vars}
do
	print_info 2 "Saving ${v} to override defaults"
	newvar=override_${v}
	eval "${newvar}='${!v}'"
done

determine_real_args

if isTrue "${BUILD_RAMDISK}" && ! isTrue "${SANDBOX}"
then
	print_warning 1 '' 1 0
	print_warning 1 "${BOLD}WARNING:${NORMAL} Will build initramfs ${BOLD}without${NORMAL} sandbox support " 0

	SANDBOX_WARNING_TIMER=0
	while [[ ${SANDBOX_WARNING_TIMER} -lt 5 ]]
	do
		printf '.'
		sleep 1
		let SANDBOX_WARNING_TIMER=${SANDBOX_WARNING_TIMER}+1
	done

	print_warning 1 '' 1 0
fi

print_info 2 '' 1 0
print_info 2 "Sourcing default modules_load from '${GK_SHARE}/defaults/modules_load' ..."
source "${GK_SHARE}/defaults/modules_load" || gen_die "Could not read '${GK_SHARE}/defaults/modules_load'!"

# Read arch-specific config
print_info 2 "Sourcing arch-specific config.sh from '${ARCH_CONFIG}' ..."
source "${ARCH_CONFIG}" || gen_die "Could not read '${ARCH_CONFIG}'!"
_MODULES_LOAD="${GK_SHARE}/arch/${ARCH}/modules_load"
if [ -f "${_MODULES_LOAD}" ]
then
	print_info 2 "Sourcing arch-specific modules_load from '${_MODULES_LOAD}' ..."
	source "${_MODULES_LOAD}" || gen_die "Could not read '${_MODULES_LOAD}'!"
else
	print_info 2 "No arch-specific modules_load found; Skipping ..."
fi
unset _MODULES_LOAD

# Now apply customizations of MODULES_*
for v in ${override_module_vars}
do
	newvar=override_${v}
	print_info 2 "Override $v, default (${!v}), new value (${!newvar})"
	eval "${v}='${!newvar}'"
done
unset v override_module_vars newvar

# Merge additional modules_load from config
for group_modules in ${!AMODULES_*}
do
	group="$(echo ${group_modules} | cut -d_ -f2)"
	eval cmodules="\$${group_modules}"
	eval MODULES_${group}=\"\${MODULES_${group}} ${cmodules}\"
	print_info 2 "<config> Merged AMODULES_${group}:'${cmodules}' into MODULES_${group}"
done
unset group group_modules

determine_KV
# $KV is now either set to the version from previous compilation,
# which would include LOCALVERSION suffix, or initialized with
# unmodified KERNEL_SOURCE version (which normally has no LOCALVERSION set).

determine_kernel_arch

determine_output_filenames

determine_kernel_config_file

setup_cache_dir

check_distfiles

KERNCACHE_IS_VALID="no"
if [ -n "${KERNCACHE}" ]
then
	gen_kerncache_is_valid
fi

if isTrue "${KERNCACHE_IS_VALID}" && ! isTrue "${CMD_INSTALL}" && ! isTrue "${BUILD_RAMDISK}"
then
	error_msg="Nothing to do: Selected action does not include building initramfs."
	error_msg+=" Because kerncache is valid, no kernel will be build."
	error_msg+=" However, due to set --no-install option, we will not even install kernel binary from kerncache."
	gen_die "${error_msg}"
fi

print_info 1 '' 1 0
print_info 1 "Working with Linux kernel ${BOLD}${KV}${NORMAL} for ${BOLD}${ARCH}${NORMAL}"
print_info 1 "Using kernel config file '${KERNEL_CONFIG}' ..."

if isTrue "${BUILD_KERNEL}" && ! isTrue "${KERNCACHE_IS_VALID}"
then
	print_info 1 ''
	print_info 1 "Note: The version above is subject to change (depends on config and status of kernel sources)."
fi

isTrue "${CMD_INSTALL}" && make_bootdir_writable

check_disk_space_requirements

if isTrue "${BUILD_KERNEL}" && ! isTrue "${KERNCACHE_IS_VALID}"
then
	print_info 1 '' 1 0

	# Configure kernel
	config_kernel

	# Make prepare
	if ! isTrue "${ARCH_HAVENOPREPARE}"
	then
		compile_generic prepare kernel
	else
		print_info 2 "$(get_indent 1)>> Skipping 'make prepare' due to ARCH_HAVENOPREPARE=yes!"
	fi

	# KV may have changed due to the configuration
	determine_KV
	if [ -f "${TEMP}/.old_kv" ]
	then
		determine_output_filenames

		old_KV=$(cat "${TEMP}/.old_kv")
		print_info 1 "$(get_indent 1)>> Kernel version has changed (probably due to config change) since genkernel start:"
		print_info 1 "$(get_indent 1)   We are now building Linux kernel ${BOLD}${KV}${NORMAL} for ${BOLD}${ARCH}${NORMAL} ..."
	else
		print_info 2 "$(get_indent 1)>> Kernel version has not changed since genkernel start"
	fi

	compile_kernel

	# Compile modules
	if isTrue "${BUILD_MODULES}" && ! isTrue "${BUILD_STATIC}"
	then
		compile_modules
		compile_external_modules
	fi

	if isTrue "${SAVE_CONFIG}"
	then
		print_info 1 "$(get_indent 1)>> Saving config of successful build to '/etc/kernels/${GK_FILENAME_CONFIG}' ..."
		[ ! -e '/etc/kernels' ] && mkdir -p /etc/kernels
		cp "${KERNEL_OUTPUTDIR}/.config" "/etc/kernels/${GK_FILENAME_CONFIG}" || \
			print_warning 1 "Unable to copy the kernel configuration file; Ignoring non-fatal error ..."
			# Just a warning because ordinary users are not allowed to write in /etc
	fi
elif [[ -n "${KERNEL_LOCALVERSION}" && "${KERNEL_LOCALVERSION}" != "${LOV}" ]]
then
	if [[ "${KERNEL_LOCALVERSION}" == "UNSET" && -z "${LOV}" ]]
	then
		# LOV is already unset...
		:
	else
		print_warning 1 '' 1 0
		print_warning 1 "Current kernel's LOCALVERSION is set to '${LOV}'; Will ignore set --kernel-localversion value '${KERNEL_LOCALVERSION}' because kernel was not build ..."
	fi
fi

if isTrue "${KERNCACHE_IS_VALID}"
then
	if isTrue "${CMD_INSTALL}"
	then
		print_info 1 '' 1 0
		gen_kerncache_extract_kernel
	fi

	if ! isTrue "${BUILD_STATIC}"
	then
		if ! isTrue "${CMD_INSTALL}" && [ -z "${INSTALL_MOD_PATH}" ]
		then
			# We have to set $INSTALL_MOD_PATH to avoid installing files
			# to /lib/modules because --no-install was set
			INSTALL_MOD_PATH="$(mktemp -d -p "${TEMP}" kerncache-modules.XXXXXXX 2>/dev/null)"
			if [ -z "${INSTALL_MOD_PATH}" ]
			then
				gen_die "Internal error: Variable 'INSTALL_MOD_PATH' is empty; mktemp() for kerncache modules failed!"
			else
				print_info 5 '' 1 0
				print_info 5 "INSTALL_MOD_PATH set to '${INSTALL_MOD_PATH}' because --kerncache is used but --no-install was set ..."
				mkdir "${INSTALL_MOD_PATH}/lib" || gen_die "Failed to create '${INSTALL_MOD_PATH}/lib'!"
			fi
		fi

		print_info 1 '' 1 0
		gen_kerncache_extract_modules
	fi

	if isTrue "${SAVE_CONFIG}"
	then
		print_info 1 '' 1 0
		gen_kerncache_extract_config
	fi
fi

# Run callback
if [ -n "${CMD_CALLBACK}" ]
then
	print_info 1 '' 1 0
	print_info 1 "Preparing to run callback: \"${CMD_CALLBACK}\"" 0

	CALLBACK_ESCAPE=0
	CALLBACK_COUNT=0

	trap "CALLBACK_ESCAPE=1" TERM KILL INT QUIT ABRT
	while [[ "${CALLBACK_ESCAPE}" -eq 0 && ${CALLBACK_COUNT} -lt 5 ]]
	do
		sleep 1; printf '.';
		let CALLBACK_COUNT=${CALLBACK_COUNT}+1
	done

	if [ "${CALLBACK_ESCAPE}" -eq 0 ]
	then
		print_info 1 '' 1 0
		print_info 1 '' 1 0
		eval ${CMD_CALLBACK} | tee -a "${LOGFILE}"
		CMD_STATUS="${PIPESTATUS[0]}"
		print_info 1 '' 1 0
		print_info 1 "<<< Callback exit status: ${CMD_STATUS}"
		[ "${CMD_STATUS}" -ne 0 ] && gen_die '--callback failed!'
	else
		print_warning 1 '' 1 0
		print_warning 1 ">>> Callback cancelled ..."
	fi

	# restore default trap
	set_default_gk_trap
fi

if isTrue "${BUILD_RAMDISK}"
then
	print_info 1 '' 1 0

	# Compile initramfs
	create_initramfs
else
	print_info 1 '' 1 0
	print_info 1 "initramfs: >> Not building since only the kernel was requested ..."
fi

if isTrue "${INTEGRATED_INITRAMFS}"
then
	print_info 1 '' 1 0

	cfg_CONFIG_INITRAMFS_SOURCE=$(kconfig_get_opt "${KERNEL_OUTPUTDIR}/.config" "CONFIG_INITRAMFS_SOURCE")
	if [[ "${cfg_CONFIG_INITRAMFS_SOURCE}" != "\"${CPIO_ARCHIVE}.cpio\"" ]]
	then
		gen_die "Sanity check failed: CONFIG_INITRAMFS_SOURCE is not set to '${CPIO_ARCHIVE}.cpio' in '${KERNEL_OUTPUTDIR}/.config'!"
	fi
	unset cfg_CONFIG_INITRAMFS_SOURCE

	# We build the kernel a second time to include the initramfs
	compile_kernel
fi

if [ -n "${KERNCACHE}" ]
then
	if ! isTrue "${KERNCACHE_IS_VALID}"
	then
		# Only update KERNCACHE when KERNCACHE wasn't used because
		# when it was used nothing has been changed so no update is
		# necessary.
		print_info 1 '' 1 0
		gen_kerncache
	else
		print_info 3 '' 1 0
		print_info 3 "kerncache: >> Existing kerncache was used and kernel/modules therefore didn't change; Skipping '${KERNCACHE}' generation ..."
	fi
fi

if [ -n "${MINKERNPACKAGE}" ]
then
	print_info 1 '' 1 0
	gen_minkernpackage
fi

if [ -n "${MODULESPACKAGE}" ]
then
	print_info 1 '' 1 0
	gen_modulespackage
fi

if isTrue "${BUILD_KERNEL}"
then
	show_warning_initramfs_is_required=yes

	print_info 1 '' 1 0
	print_info 1 'Kernel compiled successfully!'

	if isTrue "${CMD_INSTALL}"
	then
		set_bootloader

		# When we have installed kernel and initramfs *and*
		# updated bootloader, we can assume that initramfs will
		# be used...
		case "${BOOTLOADER}" in
			grub)
				show_warning_initramfs_is_required=no
				;;
			grub2)
				show_warning_initramfs_is_required=no
				;;
		esac
	else
		print_info 1 ''

		if ! isTrue "${KERNCACHE_IS_VALID}"
		then
			print_info 1 "You will find the kernel image in '${TMPDIR}/${GK_FILENAME_TEMP_KERNEL}'."

			if isTrue "${GENZIMAGE}"
			then
				print_info 1 "You will find the kernelz binary in '${TMPDIR}/${GK_FILENAME_TEMP_KERNELZ}'."
			fi
		fi

		if isTrue "${BUILD_RAMDISK}"
		then
			if isTrue "${INTEGRATED_INITRAMFS}"
			then
				show_warning_initramfs_is_required=no
				print_info 1 "Initramfs is integrated into kernel image."
			else
				print_info 1 "You will find the initramfs in '${TMPDIR}/${GK_FILENAME_TEMP_INITRAMFS}'."
			fi
		fi
	fi

	if isTrue "${CMD_INSTALL}" || ! isTrue "${KERNCACHE_IS_VALID}"
	then
		print_info 1 ''
		print_info 1 'Required kernel parameter:'
		print_info 1 ''
		print_info 1 '	root=/dev/$ROOT'
		print_info 1 ''
		print_info 1 'Where $ROOT is the device node for your root partition as the'
		print_info 1 'one specified in /etc/fstab'

		if isTrue "${INTEGRATED_INITRAMFS}" && isTrue "${BUILD_RAMDISK}"
		then
			show_warning_initramfs_is_required=no
			print_info 1 ''
			print_info 1 "Initramfs is integrated into kernel image."
		fi
	fi

	if isTrue "${show_warning_initramfs_is_required}" && isTrue "${BUILD_RAMDISK}"
	then
		INITRAMFS_FILE="${TMPDIR}/${GK_FILENAME_TEMP_INITRAMFS}"
		isTrue "${CMD_INSTALL}" && INITRAMFS_FILE="${BOOTDIR}/${GK_FILENAME_INITRAMFS}"

		print_warning 1 '' 1 0
		print_warning 1 "If you require Genkernel's hardware detection features, you ${BOLD}MUST${NORMAL}"
		print_warning 1 "tell your bootloader to use the provided initramfs file '${INITRAMFS_FILE}'."
		unset INITRAMFS_FILE
	fi
	unset show_warning_initramfs_is_required
fi

if isTrue "${BUILD_RAMDISK}"
then
	if ! isTrue "${BUILD_KERNEL}"
	then
		INITRAMFS_FILE="${TMPDIR}/${GK_FILENAME_TEMP_INITRAMFS}"
		isTrue "${CMD_INSTALL}" && INITRAMFS_FILE="${BOOTDIR}/${GK_FILENAME_INITRAMFS}"

		print_info 1 ''
		print_info 1 "You will find the initramfs in '${INITRAMFS_FILE}'."
		unset INITRAMFS_FILE
	fi

	print_warning 1 '' 1 0
	print_warning 1 "${BOLD}WARNING... WARNING... WARNING...${NORMAL}"
	print_warning 1 'Additional kernel parameters that *may* be required to boot properly:'
	isTrue "${SPLASH}"    && print_warning 1 "- Add \"vga=791 splash=silent,theme:${SPLASH_THEME} console=tty1 quiet\" if you use a splash framebuffer ]"
	isTrue "${BCACHE}"    && print_warning 1 '- Add "dobcache" for bcache support'
	isTrue "${BTRFS}"     && print_warning 1 '- Add "dobtrfs" for Btrfs device scanning support'
	isTrue "${MULTIPATH}" && print_warning 1 '- Add "domultipath" for multipath support'
	isTrue "${ISCSI}"     && print_warning 1 '- For iSCSI support, add at least:'
	isTrue "${ISCSI}"     && print_warning 1 '	- "iscsi_initiatorname=<initiator name>"'
	isTrue "${ISCSI}"     && print_warning 1 '	- "iscsi_target=<target name>"'
	isTrue "${ISCSI}"     && print_warning 1 '	- "iscsi_address=<target ip>"'
	isTrue "${DMRAID}"    && print_warning 1 '- Add "dodmraid" for dmraid support or "dodmraid=<additional options>"'
	isTrue "${MDADM}"     && print_warning 1 '- Add "domdadm" for MDRAID support'
	isTrue "${LVM}"       && print_warning 1 '- Add "dolvm" for LVM support'
	isTrue "${LUKS}"      && print_warning 1 '- Add "crypt_root=<device>" for LUKS-encrypted root'
	isTrue "${LUKS}"      && print_warning 1 '- Add "crypt_swap=<device>" for LUKS-encrypted swap'
	isTrue "${SSH}"       && print_warning 1 '- Add "dosshd" to start SSH daemon in initramfs'

	if isTrue "${ZFS}"
	then
		print_warning 1 '- Add "dozfs" for ZFS volume management support'
		print_warning 1 '  and either "root=ZFS" to use bootfs autodetection or "root=ZFS=<dataset>"'
		print_warning 1 '  to force booting from a specific dataset'
		print_warning 1 ''
		if [ -f "${TEMP}/.embedded_hostid" ]
		then
			saved_hostid=$(cat "${TEMP}/.embedded_hostid")
			print_warning 1 "Hostid '${saved_hostid}' is embedded into initramfs."
			print_warning 1 "If you will use this initramfs for a different system you MUST set 'spl_hostid=<hostid>' parameter to overwrite embedded hostid!"
			unset saved_hostid
		else
			print_warning 1 "No hostid embedded into initramfs. You MUST set 'spl_hostid=<hostid>' parameter to provide hostid for ZFS!"
		fi
		print_warning 1 ''
		print_warning 1 'If importing ZFS pool is slow, add dozfs=cache or dozfs=force to kernel commandline.'
		print_warning 1 '"man genkernel" explains "dozfs" in detail.'
	fi

	if isTrue "$(is_gzipped "${KERNEL_CONFIG}")"
	then
		# Support --kernel-config=/proc/config.gz, mainly
		CONFGREP=zgrep
	else
		CONFGREP=grep
	fi

	if [ $(${CONFGREP} 'CONFIG_EXT[0-9]_FS=' "${KERNEL_CONFIG}" | wc -l) -ge 2 ]
	then
		print_warning 1 ''
		print_warning 1 'With support for several ext* filesystems available, it may be needed to'
		print_warning 1 'add "rootfstype=ext3" or "rootfstype=ext4" to the list of boot parameters.'
	fi

	unset CONFGREP
fi

isTrue "${CMD_INSTALL}" && restore_boot_mount_state

print_info 1 '' 1 0
print_info 1 'Do NOT report kernel bugs as genkernel bugs unless your bug'
print_info 1 'is about the default genkernel configuration...'
print_info 1 ''
print_info 1 'Make sure you have the latest ~arch genkernel before reporting bugs.'

# Final Cleanup
cleanup
